properties-file
.properties
file JSON converter, serializer, parser and Webpack loader.
Installation 💻
⚠ in June 2022 we have released version 2 of this package which is not compatible with the previous versions. Make sure to read the documentation before upgrading.
Add the package as a dependency:
npm install properties-file
What's in it for me? 🤔
- A modern TypeScript library that reproduces exactly the Properties Java implementation.
- Flexible APIs:
propertiesToJson
allows quick conversion from .properties
files to JSON.getProperties
returns a Properties
object that provides insights into parsing issues such as key collisions.propertiesToJson
& getProperties
also have a browser-compatible version when passing directly the content of a file using the APIs under properties-file/content
.escapeKey
, escapeValue
that can allow you to convert any content to .properties
compatible format.- Out of the box Webpack loader to
import
.properties
files directly in your application.
- 100% test coverage based on the output from a Java implementation.
- Active maintenance (many popular .properties packages have been inactive years).
Usage 🎬
We put a lot of effort into adding TSDoc to all our APIs. Please check directly in your IDE if you are unsure how to use certain APIs provided in our examples.
Both APIs (getProperties
and propertiesToJson
) directly under properties-file
depend on fs
which means they cannot be used by browsers. If you cannot use fs
and already have a .properties
file content, the same APIs are available under properties-file/content
. Instead of taking the filePath
as the first argument, they take content
. The example below will use "fs
" APIs since they are the most common use cases.
propertiesToJson
(common use case)
This API is probably the most used. You have a .properties
file that you want to open and access like a simple key/value JSON object. Here is how this can be done with a single API call:
import { propertiesToJson } from 'properties-file'
console.log(propertiesToJson('hello-world.properties'))
Output:
{ hello: 'hello', world: 'world' }
If you cannot use fs (e.g., from a browser) and already have the content of a .properties
file, your code would look like this instead:
import { propertiesToJson } from 'properties-file/content'
console.log(propertiesToJson(propertiesFileContent))
escapeKey
and escapeValue
(serializing content to .properties
format)
⚠ This package does not offer a full-fledged .properties
file writer that would include a variety of options like modifying an existing file while keeping comments and line breaks intact. If you have any interest into adding this in, pull requests are welcomed!
It is possible to use this package serialize content to .properties.
format by using escapeKey
and escapeValue
. Here is an example of how it can be done:
import * as fs from 'node:fs'
import { EOL } from 'node:os'
import { getProperties } from 'properties-file'
import { escapeKey, escapeValue } from 'properties-file/escape'
const properties = getProperties('assets/tests/collisions-test.properties')
const newProperties: string[] = []
console.dir(properties)
properties.collection.forEach((property) => {
const value = property.value === 'world3' ? 'new world3' : property.value
newProperties.push(`${escapeKey(property.key)}: ${escapeValue(value)}`)
})
fs.writeFileSync('myNewFile.properties', newProperties.join(EOL))
getProperties
(advanced use case)
Java's implementation of Properties
is quite resilient. In fact, there are only two ways an exception can be thrown:
- The file is not found.
- A (
\u
) Unicode escape character is malformed.
This means that almost all files will be valid.
But what about a file that has duplicate keys? Duplicate keys have no reason to exist and they probably should have thrown errors as well but instead Java decided to simply overwrite the value with the latest occurrence in a file.
So how can we know if there were duplicate keys if we want to log some warnings? Simply by using getProperties
which will return all the data that was used to parse the content. Here is an example on how it can be used:
# collisions-test.properties
hello: hello1
world: world1
world: world2
hello: hello2
world: world3
import { getProperties } from 'properties-file'
const properties = getProperties('assets/tests/collisions-test.properties')
properties.collection.forEach((property) => {
console.log(`${property.key} => '${property.value}'`)
})
const keyCollisions = properties.getKeyCollisions()
keyCollisions.forEach((keyCollision) => {
console.warn(
`Found a key collision for key '${
keyCollision.key
}' on lines ${keyCollision.startingLineNumbers.join(
', '
)} (will use the value at line ${keyCollision.getApplicableLineNumber()}).`
)
})
Webpack File Loader
If you would like to import .properties
directly using import
, this package comes with its own Webpack file loader located under properties-file/webpack-loader
. Here is an example of how to configure it:
module.exports = {
module: {
rules: [
{
test: /\.properties$/i,
use: [
{
loader: 'properties-file/webpack-loader',
},
],
},
],
},
}
As soon as you configure Webpack, the .properties
type should be available in your IDE when using import
. If you ever need to add it manually, you can add a *.properties
type declaration file at the root of your application, like this:
declare module '*.properties' {
const properties: { readonly [key: string]: string };
export default properties;
}
By adding these configurations you should now be able to import directly .properties
files just like this:
import helloWorld from './hello-world.properties'
console.dir(helloWorld)
Output:
{ "hello": "world" }
Why another .properties
file package?
There are probably over 20 similar packages available but:
- A lot of the most popular packages have had no activity for over 5 years.
- A large portion of the packages will not replicate the current Java implementation.
- No package offers the same capabilities as this one.
Unfortunately the .properties
file specification is not well documented. One reason for this is that it was originally used in Java to store configurations. Most applications will handle this using JSON, YAML or other modern formats today because the formats are more flexible.
So why .properties
files?
While many options exists today to handle configurations, .properties
file remain one of the best option to store localizable strings (also known as messages). On the Java side, PropertyResourceBundle
is how most implementations handle localization today. Because of its simplicity and maturity, .properties
files remain one of the best options today when it comes to internationalization (i18n):
File format | Key/value based | Supports inline comments | Built for localization | Good linguistic tools support |
---|
.properties | Yes | Yes | Yes (Resource Bundles) | Yes |
JSON | No (can do more) | No (requires JSON5) | No | Depends on the schema |
YAML | No (can do more) | Yes | No | Depends on the schema |
By having good JavaScript/TypeScript support for .properties
files, it provides more options when it comes to i18n.
How does this package work?
Basically our goal was to offer parity with the Java implementation, which is the closest thing to a specification .properties
file have. Here is in a nutshell the logic behind this package:
- Split the file content by lines (create line objects)
- Create
LineObjects
by combining multi-line properties and removing trailing backslash - Create
PropertyObjects
from LineObjects
that combined all lines of a property - Identify the key/value delimiter and populate escaped keys and values.
- Unescape keys and values
- Create a
PropertiesObject
that will include all PropertyObjects
while removing collisions
Just like Java, if a Unicode escaped characters (\u
) is malformed, it will throw an error. But of course, we do not recommend using Unicode escaped characters but rather UTF-8 encoding that supports more characters.
Additional references
Special mention
Thanks to @calibr, the creator of properties-file version 1.0, for letting us use the https://www.npmjs.com/package/properties-file package name. We hope that it will make it easier to find our package.